﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Compiler.TableOfSymbols.Types;
using Compiler.Unification.Substitutions;

namespace Compiler.TableOfSymbols
{
    /// <summary>
    /// Биективное отображение информации о типах NET (System.Type)
    /// и объектов типов, используемых нами
    /// </summary>
    public class NETTypesBijectiveMap
    {
        private Dictionary<BaseType, Type> toNetMap;
        private Dictionary<Type, BaseType> fromNetMap;

        public Type this[BaseType key]
        { get { return toNetMap[key]; } }
        public BaseType this[Type key]
        { get { return fromNetMap[key]; } }

        public const char GENERIC_TYPE_NAME_DELIM = '`';

        /// <summary>
        /// Создаёт объект типа, используемого нами, для заданного типа NET
        /// </summary>
        /// <param name="NETtypeInfo">Информация System.Type о типе</param>
        /// <returns>Объект типа, используемый нами</returns>
        public BaseType MakeTypeInfo(Type NETtypeInfo)
        {
            if (NETtypeInfo.IsGenericParameter)
                return new TypeVariable(NETtypeInfo.Name);
            else if (NETtypeInfo.IsGenericTypeDefinition)
                return new GenericTypeDefinition(NETtypeInfo.Name);
            else if (NETtypeInfo.IsGenericType)
            {
                Type NETtypeDef = NETtypeInfo.GetGenericTypeDefinition();
                System.Diagnostics.Debug.Assert(fromNetMap.ContainsKey(NETtypeDef),
                    "Generic definition must be in map");
                GenericTypeDefinition typeDef = fromNetMap[NETtypeDef] as GenericTypeDefinition;
                System.Diagnostics.Debug.Assert(typeDef != null,
                    "Generic definition typeDef must be GenericTypeDefinition");
                if (NETtypeInfo.ContainsGenericParameters)
                    return new GenericVarType(NETtypeInfo.Name, typeDef);
                else
                    return new GenericType(NETtypeInfo.Name, typeDef);
            }
            // simple type
            else
                return new SimpleType(NETtypeInfo.Name);
        }

        public NETTypesBijectiveMap()
        {
            toNetMap = new Dictionary<BaseType, Type>();
            fromNetMap = new Dictionary<Type, BaseType>();
        }

        public bool Contains(BaseType typeInfo)
        {
            return toNetMap.ContainsKey(typeInfo);
        }
        public bool Contains(Type NETtypeInfo)
        {
            return fromNetMap.ContainsKey(NETtypeInfo);
        }

        public void AddPair(BaseType typeInfo, Type NETtypeInfo)
        {
            System.Diagnostics.Debug.Assert(typeInfo != null,
                String.Format("typeInfo can not be null"));
            System.Diagnostics.Debug.Assert(NETtypeInfo != null,
                String.Format("NETtypeInfo can not be null"));
            toNetMap.Add(typeInfo, NETtypeInfo);
            fromNetMap.Add(NETtypeInfo, typeInfo);
        }

        public void AddPairByNETType(Type NETtypeInfo)
        {
            System.Diagnostics.Debug.Assert(NETtypeInfo != null,
                String.Format("NETtypeInfo can not be null"));
            System.Diagnostics.Debug.Assert(!fromNetMap.ContainsKey(NETtypeInfo),
                String.Format("NETtypeInfo must be new (unused) type"));
            BaseType typeInfo = MakeTypeInfo(NETtypeInfo);
            this.AddPair(typeInfo, NETtypeInfo);
        }

        public BaseType GetTypeInfoWithPossibleAdd(Type NETtypeInfo)
        {
            if (!this.Contains(NETtypeInfo))
                AddPairByNETType(NETtypeInfo);
            return this[NETtypeInfo];
        }

        public string GetTypeName(BaseType t)
        {
            if (t.IsTypeValue)
                if (t is ClassTypeValueNET)
                    return this[t].ToString();
            return t.ToString();
        }

        public string GetFullDescription(BaseType t)
        {
            if (t.IsTypeVariable)
            {
                TypeVariable tv = t as TypeVariable;
                return String.Format("{0} (maxLower: {1}, minUpper: {2})", tv,
                    GetFullDescription(tv.MaxLowerType), GetFullDescription(tv.MinUpperType));
            }
            else if (t is ClassTypeValueNET)
                return this[t].ToString();
            return t.ToString();
        }

        public BaseType TypeSubstitution(BaseType typeInfo, SubstitutionItem subsItem)
        {
            if (typeInfo == subsItem.FromVariable)
                return subsItem.ToType;
            else if (typeInfo.IsTypeVariable)
            {
                // применяем подстановку к границам переменной
                TypeVariable tv = typeInfo as TypeVariable;
                TypeValue maxLower = TypeSubstitution(tv.MaxLowerType, subsItem) as TypeValue;
                TypeValue minUpper = TypeSubstitution(tv.MinUpperType, subsItem) as TypeValue;
                System.Diagnostics.Debug.Assert(maxLower != null,
                    "maxLower must be TypeValue after substitution");
                System.Diagnostics.Debug.Assert(minUpper != null,
                    "minUpper must be TypeValue after substitution");
                tv.MaxLowerType = maxLower;
                tv.MinUpperType = minUpper;
                return tv;
            }
            // typeInfo is TypeValue
            TypeValue typeValInfo = typeInfo as TypeValue;
            if (!typeValInfo.IsNETClassTypeValue)
                return typeInfo;
            ConstructedGenericType typeGenVarInfo = typeValInfo as ConstructedGenericType;
            if ((typeGenVarInfo != null) && typeGenVarInfo.IsGenericVarType)
            {
                Type typeGenVarInfoNET = this[typeGenVarInfo];
                System.Diagnostics.Debug.Assert(typeGenVarInfoNET.ContainsGenericParameters,
                    "GenVerType must contain generic params");
                Type[] argsNET = typeGenVarInfoNET.GenericTypeArguments;
                BaseType[] args = argsNET.Select(
                    t => this.GetTypeInfoWithPossibleAdd(t)).ToArray();
                BaseType[] substitutedArgs = args.Select(
                    t => TypeSubstitution(t, subsItem)).ToArray();
                Type[] substitutedArgsNET = substitutedArgs.Select(
                    t => this[t]).ToArray();
                Type typeGenDefNET = typeGenVarInfoNET.GetGenericTypeDefinition();
                Type resultType = typeGenDefNET.MakeGenericType(substitutedArgsNET);
                return this.GetTypeInfoWithPossibleAdd(resultType);
            }
            else
                return typeInfo;
        }
    }
}
